package graph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import grammar.Design;

public class GraphMatch {
    private Map<GNode, GNode> mapping = new HashMap<GNode,GNode>(); 
    private Design graphToFind;
    private Design graphToSearch;
    
    public String toString() {
        String s = "\n\nMATCH";
        for (GNode key : mapping.keySet()) {
            GNode val = mapping.get(key);
            s = s.concat("\n").concat(key.getLabelsString()).concat(" ").concat(key.getDebugId()).concat(" --> "
                    ).concat(val.getLabelsString()).concat(" ").concat(val.getDebugId());
        }
        return s;
    }
    
    public GNode getToFindMatch(GNode toFindNode) {
        return mapping.get(toFindNode);
    }
    
    public GraphMatch(Design graphToFind, Design graphToSearch) {
        this.graphToFind = graphToFind;
        this.graphToSearch = graphToSearch;
    }
    
    public void addMatch(GNode nodeToFind, GNode nodeToSearch) {
        if (!graphToFind.containsVertex(nodeToFind)) {
            System.out.print("Trying to build match with node not in graph to find!!!");
        }
        if (!graphToSearch.containsVertex(nodeToSearch)) {
            System.out.print("Trying to build match with node not in graph to search!!!");
        }
        if (mapping.containsKey(nodeToFind)) {
            System.out.print("The key is already mapped!!");
        }
        if (mapping.containsValue(nodeToSearch)) {
            System.out.print("The value is already mapped!!");
        }
        
        mapping.put(nodeToFind, nodeToSearch);
    }

    public boolean isComplete(int numberMatches) {
        return numberMatches == mapping.keySet().size();
    }

    public boolean isSame(GraphMatch match) {
        if(this.graphToFind != match.graphToFind || this.graphToSearch != match.graphToSearch) {
            return false;
        }
        for (GNode node : this.graphToFind.getVertices()) {
            if (match.mapping.get(node) != this.mapping.get(node)) {
                return false;
            }
        }
        return true;
    }

    public List<GNode> getUnmatchedToFindNeighbors() {
        Set<GNode> allNeighbors = new HashSet<GNode>();
        for (GNode node : this.mapping.keySet()) {
            allNeighbors.addAll(this.graphToFind.getNeighbors(node));
        }
        allNeighbors.removeAll(this.mapping.keySet());
        return new ArrayList<GNode>(allNeighbors);
    }

    public List<GNode> findMatchedToFindNeighbors(GNode nodeToFind) {
        List<GNode> neighbors = new ArrayList<GNode>(this.graphToFind.getNeighbors(nodeToFind));
        neighbors.retainAll(mapping.keySet());
        return neighbors;
    }

    public GraphMatch copy() {
        GraphMatch copy = new GraphMatch(graphToFind, graphToSearch);
        for(GNode key : this.mapping.keySet()) {
            GNode value = this.mapping.get(key);
            copy.mapping.put(key, value);
        }
        return copy;
    }

    public GraphMatch addMatch(GraphMatch match) {
        GraphMatch sum = new GraphMatch(this.graphToFind, this.graphToSearch);
        sum.mapping.putAll(this.mapping);
        sum.mapping.putAll(match.mapping);
        return sum;
    }

    public int size() {
        return mapping.size();
    }

    public GNode getToSearchMatch(GNode toFindNode) {
        for (Entry<GNode, GNode> entry : mapping.entrySet()) {
            if (entry.getValue() == toFindNode) {
                return entry.getKey();
            }
        }
        return null;
    }

    public Collection<GNode> matchedNodes() {
        return mapping.values();
    }

    public Collection<GNode> keyNodes() {
        return mapping.keySet();
    }
}
